<?php

namespace BkLink;
/*
 * @title 断网重连继续sql操作
 * @author millionmile<810185932@qq.com>
 */

class BreaklinkReconnection
{
    static $sleepTime = 5;    //每次睡眠暂停的时间
    static $dbObj = [];   //存放最新的dB连接
    static $intervalNum = 1;    //每隔一段时间重新执行
    private static $currentDbConnect = [];
    private static $defualtConfig = [
        'simpleReturn' => 1,    //简单格式返回
        'dbConnect' => [],
        'forceDbConnFresh' => false,
        'ignoreNotify' => true,
        'customOperate' => false //
    ];


    /**
     * 初始化默认的断线重连参数
     * @param array $config
     */
    public static function setConfig($config = [])
    {
        self::$defualtConfig['simpleReturn'] = $config['simpleReturn'] ?? self::$defualtConfig['simpleReturn'];
        self::$defualtConfig['dbConnect'] = $config['dbConnect'] ?? self::$defualtConfig['dbConnect'];
        self::$defualtConfig['forceDbConnFresh'] = $config['forceDbConnFresh'] ?? self::$defualtConfig['forceDbConnFresh']; //强制重连刷新
        self::$defualtConfig['ignoreNotify'] = $config['ignoreNotify'] ?? self::$defualtConfig['ignoreNotify']; //是否忽略重连通知
        self::$defualtConfig['customOperate'] = $config['customOperate'] ?? self::$defualtConfig['customOperate']; //是否使用自定义的sql查询操作
    }

    /**
     * @title 切换连接的数据库，保证该连接是可用的
     * @param null $dbObj
     */
    public static function getDbObj($dbConnect = [], $forceFresh = false)
    {
        return self::reconnCb(
            function () use ($dbConnect, $forceFresh) {
                //切换为新的数据库连接
                return db('', $dbConnect, $forceFresh);
            },
            function () use ($dbConnect) {
                return self::getDbObj($dbConnect, true);
            }, [
                'resetIntervalNumFlag' => false    //避免影响到定时间隔
            ]
        );
    }

    /**
     * @title 服务器断开的话的断线重连
     * @param $queryCb callable|string 回调函数形式的话，最终也要返回字符串的sql
     * @param array $config 配置项，可不填写
     *               simpleReturn       返回的数据格式为简单格式，不返回具体错误信息，直接获取sql会返回的数据集
     *               dbConnect          连接数据库的配置项，可以用来选择要连接的库
     *               forceDbConnFresh   强制重连，默认一开始进入不需要重连，无需人工配置
     *               ignoreNotify       是否查看连接数据库信息
     * @return array|bool|mixed
     */
    public static function breaklineReconnection($queryCb, $config = [])
    {
        //如果直接返回值，simpleReturn为1
        $simpleReturn = $config['simpleReturn'] ?? self::$defualtConfig['simpleReturn'];
        $dbConnect = $config['dbConnect'] ?? self::$defualtConfig['dbConnect'];
        $forceDbConnFresh = $config['forceDbConnFresh'] ?? self::$defualtConfig['forceDbConnFresh']; //强制重连刷新
        $ignoreNotify = $config['ignoreNotify'] ?? self::$defualtConfig['ignoreNotify']; //是否忽略重连通知

        if (is_array($dbConnect)) {
            $dbConnectStr = md5(json_encode($dbConnect));
        } else {
            $dbConnectStr = $dbConnect;
        }
        //如果不是当前的，需要重新初始化db

        //重新获取dbObj
        if (self::$dbObj[$dbConnectStr] === null || $forceDbConnFresh) {
            if ((!$ignoreNotify) && $forceDbConnFresh) {
                echo '重连数据库' . PHP_EOL;
            }
            self::$dbObj[$dbConnectStr] = self::getDbObj($dbConnect, $forceDbConnFresh);
        }

        return self::reconnCb(
            function () use ($queryCb, &$config, $simpleReturn, $forceDbConnFresh, $ignoreNotify, $dbConnectStr) {
                //如果不是使用自定义操作，则传sql
                switch (true) {
                    //如果是回调函数，就是使用直接执行方法
                    case is_callable($queryCb) === true:
                        $data = $queryCb(self::$dbObj[$dbConnectStr]); //如有特殊需要，如切换数据库，建议使用内置getDbObj方法获取
                        break;
                    //如果是sql语句
                    case is_string($queryCb) === true:
                        $data = self::$dbObj[$dbConnectStr]->query($queryCb);
                        break;
                    default:
                        if ($simpleReturn) {
                            return false;
                        } else {
                            return [
                                'code' => 0,
                                'msg' => '暂不支持的sql执行方式，请使用字符串或回调函数',
                                'data' => []
                            ];
                        }
                        break;
                }
                unset($config['forceDbConnFresh']); //取消强制重连
                if ($simpleReturn) {
                    return $data;
                } else {
                    return [
                        'code' => 0,
                        'msg' => '执行成功，返回结果',
                        'data' => $data
                    ];
                }
            },
            function () use ($queryCb, &$config) {
                $config['forceDbConnFresh'] = true; //重连连接数据库
                return self::breaklineReconnection($queryCb, $config);
            },
            [
                'simpleReturn' => $simpleReturn,
                'dbConnect' => $dbConnect,
                'forceDbConnFresh' => $forceDbConnFresh,
                'ignoreNotify' => $ignoreNotify,
            ]
        );
    }


    /**
     * @title 重连回调函数执行
     * @param callable $execCb
     * @param callable $errCb
     * @param array $config
     * @return mixed
     */
    private static function reconnCb(callable $execCb, callable $errCb, array $config = [])
    {
        $ignoreNotify = $config['ignoreNotify'] ?? self::$defualtConfig['ignoreNotify'];
        $resetIntervalNumFlag = $config['resetIntervalNumFlag'] ?? true;    //重置间隔数字

        $errSelfCb = function () use ($errCb, $ignoreNotify) {
            $currentSleepTime = intval(self::$sleepTime * self::$intervalNum);
            //每次重连的间隔延长
            self::$intervalNum++;
            if (!$ignoreNotify) {
                echo '连接数据库失败，' . $currentSleepTime . '秒后重新执行' . PHP_EOL;
            }
            sleep($currentSleepTime);
            return $errCb();
        };

        try {
            $res = $execCb();
            //上面执行成功，重新调整失败重连间隔时间
            if ($resetIntervalNumFlag) {
                self::$intervalNum = 1;
            }
            return $res;
        } catch (\Exception $e) {
            //如果是mysql连接失败，重新执行方法
            $errMsg = $e->getMessage();
            if (strpos($errMsg, 'SQLSTATE[HY000]') !== false && (strpos($errMsg, '2002') !== false or strpos($errMsg, '2006') !== false)) {
                return $errSelfCb();
            }
            echo $errMsg;
            exit;
        }
    }
}